/*
 * $RCSfile: ClassScanner.java,v $
 * $Revision: 1.1 $
 * $Date: 2015-06-02 16:33:25 $
 *
 * Copyright (C) 2008 Skin, Inc. All rights reserved.
 *
 * This software is the proprietary information of Skin, Inc.
 * Use is subject to license terms.
 */
package com.skin.demon;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

/**
 * <p>Title: ClassScanner</p>
 * <p>Description: </p>
 * <p>Copyright: Copyright (c) 2006</p>
 * @author xuesong.net
 * @version 1.0
 */
public class ClassScanner {
    /**
     * @param args
     */
    public static void main(String[] args) {
        ClassScanner cs = new ClassScanner();

        try {
            cs.getTreeXml2();
        }
        catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * @throws IOException
     * @throws Exception
     */
    public void getTreeXml2() throws Exception {
        String packageName = "com.skin";
        List<PackageInfo> childs = this.getChildList(packageName);

        for(PackageInfo packageInfo : childs) {
            System.out.println(packageInfo.getPackageName() + "." + packageInfo.getName());
        }
    }

    /**
     * @param name
     * @return List<String>
     * @throws IOException
     */
    public List<String> getLocations(String name) throws IOException {
        List<String> locations = new ArrayList<String>();
        java.util.Enumeration<URL> urls = this.getClass().getClassLoader().getResources(name);

        while(urls.hasMoreElements()) {
            String urlPath = urls.nextElement().getFile();

            try {
                urlPath = URLDecoder.decode(urlPath, "utf-8");
            }
            catch(UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }

            if(urlPath.startsWith("file:")) {
                urlPath = urlPath.substring(5);
            }

            if(urlPath.indexOf('!') > 0) {
                urlPath = urlPath.substring(0, urlPath.indexOf('!'));
            }
            locations.add(new File(urlPath).getAbsolutePath());
        }
        return locations;
    }

    /**
     * @param className
     * @return String
     */
    public String getLocation(String className) {
        try {
            Class<?> type = ServiceInvocation.getClass(className);
            URL url = type.getResource(type.getSimpleName() + ".class");
            String urlPath = URLDecoder.decode(url.getFile(), "utf-8");

            if(urlPath.startsWith("file:")) {
                urlPath = urlPath.substring(5);
            }

            if(urlPath.indexOf('!') > 0) {
                urlPath = urlPath.substring(0, urlPath.indexOf('!'));
            }
            return urlPath;
        }
        catch(Exception e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * @param className
     * @return List<MethodInfo>
     */
    public List<MethodInfo> getMethodInfoList(String className) {
        List<MethodInfo> result = new ArrayList<MethodInfo>();

        try {
            Class<?> type = ServiceInvocation.getClass(className);
            Method[] methods = type.getDeclaredMethods();

            for(Method method : methods) {
                List<String> parameterTypes = this.getClassNameList(method.getParameterTypes());
                String signature = ServiceInvocation.getMethodSignature(method);
                String description = ServiceInvocation.getMethodDescription(method);

                MethodInfo methodInfo = new MethodInfo();
                methodInfo.setClassName(className);
                methodInfo.setMethodName(method.getName());
                methodInfo.setModifiers(method.getModifiers());
                methodInfo.setParameterTypes(parameterTypes);
                methodInfo.setReturnType(method.getReturnType().getName());
                methodInfo.setSignature(signature);
                methodInfo.setDescription(description);
                result.add(methodInfo);
            }
        }
        catch(Exception e) {
            throw new RuntimeException(e);
        }
        return result;
    }

    /**
     * @param types
     * @return List<String>
     */
    private List<String> getClassNameList(Class<?>[] types) {
        List<String> result = new ArrayList<String>();

        if(types != null) {
            for(Class<?> type : types) {
                result.add(type.getName());
            }
        }
        return result;
    }
    

    /**
     * @param packageName
     * @return List<PackageInfo>
     * @throws IOException
     * @throws IOException
     */
    public List<PackageInfo> getChildList(String packageName) throws IOException {
        String path = packageName.replace('.', '/');
        java.util.Enumeration<URL> urls = this.getClass().getClassLoader().getResources(path);
        Map<String, PackageInfo> map = new HashMap<String, PackageInfo>();

        while(urls.hasMoreElements()) {
            String urlPath = urls.nextElement().getFile();

            try {
                urlPath = URLDecoder.decode(urlPath, "utf-8");
            }
            catch(UnsupportedEncodingException e) {
                throw new RuntimeException(e);
            }

            if(urlPath.startsWith("file:")) {
                urlPath = urlPath.substring(5);
            }

            if(urlPath.indexOf('!') > 0) {
                urlPath = urlPath.substring(0, urlPath.indexOf('!'));
            }

            File file = new File(urlPath);

            if(file.isDirectory()) {
                loadInDirectory(path, file, map);
            }
            else {
                loadInJar(path, file, map);
            }
        }

        List<PackageInfo> childs = new ArrayList<PackageInfo>();
        childs.addAll(map.values());

        Collections.sort(childs, new Comparator<PackageInfo>() {
            @Override
            public int compare(PackageInfo ci1, PackageInfo ci2) {
                String name1 = ci1.getPackage() + "." + ci1.getName();
                String name2 = ci2.getPackage() + "." + ci2.getName();

                if(ci1.getPackage() == ci2.getPackage()) {
                    return name1.compareTo(name2);
                }

                if(ci1.getPackage()) {
                    return -1;
                }
                else {
                    return 1;
                }
            }
        });
        return childs;
    }

    /**
     * @param parent
     * @param location
     * @param map
     */
    private void loadInDirectory(String parent, File location, Map<String, PackageInfo> map) {
        File files[] = location.listFiles();
        String packageName = parent.replace('/', '.');

        for(int i = 0, length = files.length; i < length; i++) {
            File file = files[i];
            String name = file.getName();

            if(file.isDirectory()) {
                add(map, packageName, name, true);
                continue;
            }

            if(file.getName().endsWith(".class")) {
                name = name.substring(0, name.length() - 6);
                add(map, packageName, name, false);
            }
        }
    }

    /**
     * @param parent
     * @param jarfile
     * @param map
     */
    private void loadInJar(String parent, File jarfile, Map<String, PackageInfo> map) {
        InputStream inputStream = null;
        JarInputStream jarInputStream = null;
        String packageName = parent.replace('/', '.');

        try {
            JarEntry entry = null;
            inputStream = new FileInputStream(jarfile);
            jarInputStream = new JarInputStream(new FileInputStream(jarfile));

            while((entry = jarInputStream.getNextJarEntry()) != null) {
                String path = entry.getName();

                if(path.endsWith("/")) {
                    path = path.substring(0, path.length() - 1);
                }

                if(path.length() <= parent.length() || !path.startsWith(parent)) {
                    continue;
                }

                String name = path.substring(parent.length() + 1);

                if(name.indexOf('/') > -1) {
                    continue;
                }

                if(entry.isDirectory()) {
                    add(map, packageName, name, true);
                    continue;
                }

                if(name.endsWith(".class")) {
                    add(map, packageName, name.substring(0, name.length() - 6), false);
                }
            }
        }
        catch(IOException e) {
            e.printStackTrace();
        }
        finally {
            if(jarInputStream != null) {
                try {
                    jarInputStream.close();
                }
                catch(IOException e) {
                }
            }

            if(inputStream != null) {
                try {
                    inputStream.close();
                }
                catch(IOException e) {
                }
            }
        }
    }

    /**
     * @param fqn
     * @param buffer
     */
    protected void add(Map<String, PackageInfo> map, String packageName, String name, boolean isPackage) {
        if(isPackage) {
            map.put(packageName + "." + name, new PackageInfo(packageName, name, isPackage));
        }
        else {
            if(name.indexOf('$') > -1) {
                return;
            }
            map.put(packageName + "." + name, new PackageInfo(packageName, name, isPackage));
        }
    }
}
